#include "grflib_stdafx.h"
#include "../include/StaticMesh.h"
#include "../include/VertexFormat.h"
#include <Logger.h>
#include "../include/D3D10Engine.h"
#include "../include/Texture.h"
#include <Timer.h>

namespace grflib { namespace engine { namespace aux {

CStaticMesh::CStaticMesh()
	: m_nGroups(0)
{
}

CStaticMesh::~CStaticMesh()
{
	OnDestroy();
    m_vertexBufferPtrs.clear();
	m_indexBufferPtrs.clear();
}

HRESULT CStaticMesh::CreateVertexBuffer(ID3D10Device *pd3dDevice)
{
	HRESULT hr;
	
	D3D10_BUFFER_DESC bd;
	ID3D10Buffer *vertexBuffer = 0;
	ID3D10Buffer *indexBuffer = 0;

	for (UINT group = 0; group < m_nGroups; group++)
	{
		bd.Usage = D3D10_USAGE_DEFAULT;
		bd.ByteWidth = m_vertexVec[group]->size() * sizeof(float);
		bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
		bd.CPUAccessFlags = 0;
		bd.MiscFlags = 0;
		D3D10_SUBRESOURCE_DATA InitData;
		InitData.pSysMem = &(*m_vertexVec[group])[0];
		V_RETURN( pd3dDevice->CreateBuffer(&bd, &InitData, &vertexBuffer) );
		m_vertexBufferPtrs.push_back(vertexBuffer);

		bd.Usage = D3D10_USAGE_DEFAULT;
		bd.ByteWidth = sizeof(DWORD) * (m_indexVec[group]->size());
		bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
		bd.CPUAccessFlags = 0;
		bd.MiscFlags = 0;
		InitData.pSysMem = &(*m_indexVec[group])[0];
		V_RETURN( pd3dDevice->CreateBuffer(&bd, &InitData, &indexBuffer) );
		m_indexBufferPtrs.push_back(indexBuffer);
	}

	return S_OK;
}

void CStaticMesh::UpdateGPUBuffers(ID3D10Device *pd3dDevice)
{
    for (UINT group = 0; group < m_nGroups; group++)
    {
        pd3dDevice->UpdateSubresource(m_vertexBufferPtrs[group], 0, 0, &(*m_vertexVec[group])[0], 0, 0);
        pd3dDevice->UpdateSubresource(m_indexBufferPtrs[group], 0, 0, &(*m_indexVec[group])[0], 0, 0);
    }
}

HRESULT CStaticMesh::OnCreateDevice(ID3D10Device *pd3dDevice, 
	const DXGI_SURFACE_DESC *pBufferSurfaceDesc)
{
	HRESULT hr;
	V_RETURN( CreateVertexBuffer(pd3dDevice) );

	return S_OK;
}

HRESULT CStaticMesh::PreRender(ID3D10Device *pd3dDevice,
	const D3DXMATRIX *pWorldMat,
	double fTime,
	float fElapsedTime,
    UINT pass)
{
	using namespace grflib::engine::core;
    HRESULT hr;

    bool bSucceeded = false;
    boost::shared_ptr<CEffect> pEffect;

    ////////////////////////////////////////
    //get technique
    if (pEffect = boost::dynamic_pointer_cast<CEffect>(_ResrcMan.FindObj(m_effectName)))
    {
        m_pEffect = pEffect;
        if (m_pTech = pEffect->GetTechnique(m_techniqueName))
        {
            V_RETURN( m_pTech->GetDesc(&m_techDesc) );
            bSucceeded = true;
        }
    }
    if (!bSucceeded)
    {
        W32ERR("Failed to switch to technique: %s/%s", m_effectName.c_str(), m_techniqueName.c_str());
        return E_FAIL;
    }

    ////////////////////////////////////////
    //get camera
    boost::shared_ptr<CCamera> &pCurCamera = _CamMan.GetCurrentCamera();
    if (!pCurCamera) //current camera not set
    {
        W32ERR(_T("Current camera not set!"));
        return E_FAIL;
    }

    ////////////////////////////////////////
    //set object independent shader variables
    ID3D10EffectMatrixVariable *pProjectMatVar = pEffect->GetShaderVariable("Projection")->AsMatrix();
    ID3D10EffectMatrixVariable *pViewMatVar = pEffect->GetShaderVariable("View")->AsMatrix();
    ID3D10EffectMatrixVariable *pWorldMatVar = pEffect->GetShaderVariable("World")->AsMatrix();
    if (!pProjectMatVar || !pViewMatVar || !pWorldMatVar) //uncompatible shader
    {
        W32ERR(_T("Shader uncompatible with static mesh!"));
        return E_FAIL;
    }

    V_RETURN( pProjectMatVar->SetMatrix( (float*)pCurCamera->GetDXUTCamera()->GetProjMatrix() ) );
    V_RETURN( pViewMatVar->SetMatrix( (float*)pCurCamera->GetDXUTCamera()->GetViewMatrix() ) );
    V_RETURN( pWorldMatVar->SetMatrix( (float*)pWorldMat ) );

    return S_OK;
}


HRESULT CStaticMesh::Render(ID3D10Device *pd3dDevice,
	const D3DXMATRIX *pWorldMat,
	double fTime,
	float fElapsedTime,
    UINT pass)
{
	using namespace grflib::engine::core;
    HRESULT hr;
    
    boost::shared_ptr<CEffect> pEffect;
    if (!(pEffect = m_pEffect.lock())) //hold reference until end of function
    {
        W32ERR(_T("Effect unexpectedly lost!"));
        return E_FAIL;
    }

	////////////////////////////////////////
	//draw mesh
	ID3D10EffectPass *pPass;
	D3D10_PASS_DESC passDesc;
	boost::unordered_map<UINT, std::string>::iterator materialIter;
    std::string materialName;

	for (UINT pass = 0; pass < m_techDesc.Passes; pass++)
	{
		pPass = m_pTech->GetPassByIndex(pass);
		V_RETURN( pPass->GetDesc(&passDesc) );

		for (UINT group = 0; group < m_nGroups; group ++)
		{
			//set material
			if ((materialIter = m_materials.find(group)) != m_materials.end())
			{
                if (materialName != materialIter->second) //only set shader variables when material/tech combination changed
                {
                    boost::shared_ptr<CMaterial> &pMaterial = boost::dynamic_pointer_cast<CMaterial>(_ResrcMan.FindObj(materialIter->second));
                    V_RETURN( pEffect->SetShaderVariables(pMaterial, m_techniqueName) );
                    materialName = materialIter->second;
                }
			}

			//set IA stage
			V_RETURN( _IAStage.SetInputLayout(
				pEffect->GetVSSignature(pPass),
				passDesc.pIAInputSignature,
				passDesc.IAInputSignatureSize,
				*m_vertexLayouts[group]) );
			V_RETURN( pPass->Apply(0) );

			//set IA stage status
			_IAStage.SetVertexBuffer(0, m_vertexBufferPtrs[group], m_vertexStride[group], 0);
			_IAStage.SetIndexBuffer(m_indexBufferPtrs[group], DXGI_FORMAT_R32_UINT, 0);
			_IAStage.SetPrimitiveTopology(m_primitiveTopology[group]);

			pd3dDevice->DrawIndexed(m_indexVec[group]->size(), 0, 0);
		} //end of a group
	} //end of a technique-pass

	return S_OK;
}

void CStaticMesh::DestroyVertexBuffer()
{
	for (UINT grp = 0; grp < m_vertexBufferPtrs.size(); grp++)
	{
		SAFE_RELEASE(m_vertexBufferPtrs[grp]);
		SAFE_RELEASE(m_indexBufferPtrs[grp]);
	}
    m_vertexBufferPtrs.clear();
    m_indexBufferPtrs.clear();
}

void CStaticMesh::OnDestroy()
{
	DestroyVertexBuffer();
}

void CStaticMesh::Expose(std::size_t **ppNGrp,
	std::vector< boost::shared_ptr<std::vector<float>> > **ppVertexVec,
	std::vector< boost::shared_ptr<std::vector<DWORD>> > **ppIndexVec,
	std::vector<D3D10_PRIMITIVE_TOPOLOGY> **ppTopology,
	std::vector< boost::shared_ptr<std::vector<D3D10_INPUT_ELEMENT_DESC>> > **ppLayoutVec,
	std::vector<std::size_t> **ppStrideVec,
	boost::unordered_map<UINT, std::string> **ppMaterialMap)
{
	*ppNGrp = &m_nGroups;
	*ppVertexVec = &m_vertexVec;
	*ppIndexVec = &m_indexVec;
	*ppTopology = &m_primitiveTopology;
	*ppLayoutVec = &m_vertexLayouts;
	*ppStrideVec = &m_vertexStride;
	*ppMaterialMap = &m_materials;
}

const std::vector< boost::shared_ptr<std::vector<float>> > &CStaticMesh::GetVertexVec(void)
{
    return m_vertexVec;
}

const std::vector< boost::shared_ptr<std::vector<DWORD>> > &CStaticMesh::GetIndexVec(void)
{
    return m_indexVec;
}

const std::vector< boost::shared_ptr<std::vector<D3D10_INPUT_ELEMENT_DESC>> > &CStaticMesh::GetVertexLayoutVec(void)
{
    return m_vertexLayouts;
}


engine::core::BBox<float> &CStaticMesh::GetBoundingBox()
{
	return m_bbox;
}

}}} // namespaces